<?php

namespace yphp;

use think\Exception;

/**
 * Class Query
 * @package yphp
 * Query 类
 */
class Query extends Instance
{
    # 表名
    private $table;
    # 前缀
    private $dth;
    # 数据对象
    private $data;

    function construct($table, $dth)
    {
        $this->table = $table;
        $this->dth = $dth;
        $this->data = Data::instance();
    }

    // 主表别名
    function alias($name)
    {
        $this->data->alias = $name;
        return $this;
    }

    //+----------------------------------------------------
    // 关联：左，右，内联处理
    //+----------------------------------------------------
    function left($table, $des, $dtn = null)
    {
        return $this->join($table, $des, $dtn, 'left');
    }

    function right($table, $des, $dtn = null)
    {
        return $this->join($table, $des, $dtn, 'right');
    }

    function inner($table, $des, $dtn = null)
    {
        return $this->join($table, $des, $dtn, 'inner');
    }

    function join($table, $des, $dtn = null, $type = 'inner')
    {
        $aliasArr = explode(' ', $table);
        $alias = count($aliasArr) > 1 ? $aliasArr[1] : $table;
        $table = is_null($dtn) ? $this->dth . $aliasArr[0] : $dtn . $aliasArr[0];
        $join = ['alias' => $alias, 'table' => $table, 'des' => $des, 'type' => $type];
        is_null($this->data->join) ? $this->data->join = [$join] : $this->data->join[] = $join;
        return $this;
    }

    //+----------------------------------------------------
    // 条件 where whereOr  where('id','1','eq')
    //+----------------------------------------------------
    function where()
    {
        return $this->whereCom(func_get_args(), 'AND', 'where');
    }

    function whereOr()
    {
        return $this->whereCom(func_get_args(), 'OR', 'whereOr');
    }

    protected function whereCom($args, $type, $fn)
    {
        $whereObj = Where::Instance();
        call_user_func_array([$whereObj, $fn], $args);
        list($where, $bind) = $whereObj->whereEnd();
        return $this->whereBind($where, $bind, $type);
    }

    // `balance` = `balance` - :total AND `volume` = `volume` - :total, [':total' => $total]
    function whereBind($where, $bind = null, $type = 'AND')
    {
        empty($where) || $this->data->where = is_null($w = $this->bind($bind)->data->where) ? $where : "$w $type $where";
        return $this;
    }

    //  表名、字段名转化键名 id => :id 或 a.id => :id_a
    static function bindKey($key)
    {
        $index = strpos($key, '.');
        return ':' . ($index === false ? $key : (substr($key, $index + 1) . '_' . substr($key, 0, $index)));
    }

    //  表名、字段名反引
    static function unquote($name)
    {
        return preg_replace_callback('/^((\w+)\.)?(\w+)$/', function ($match) {
            return ($match[2] ? "`{$match[2]}`." : '') . "`{$match[3]}`";
        }, $name);
    }

    // 参数绑定
    function bind($bind)
    {
        empty($bind) || $this->data->bind = is_null($b = $this->data->bind) ? $bind : array_merge($b, $bind);
        return $this;
    }

    //  字段：查 field('id, name, password')
    function field($field)
    {
        if (!empty($field)) {
            $fieldArr = [];
            foreach (explode(',', $field) as $f) {
                $fieldArr[] = self::unquote(trim($f));
            }
            $this->data->field = join(',', $fieldArr);
        }
        return $this;
    }

    //  字段：增  ['id'=>1,'name'=>'111'],.
    function values($values)
    {
        empty($values) || $this->data->values = $values;
        return $this;
    }

    //+----------------------------------------------------------------------
    //  字段(改)：常规、自增、自减、自修改、绑定、模型
    //+----------------------------------------------------------------------

    function set($field, $value = null)
    {
        return $this->setModel($field, $value, function ($field) {
            return "$field = ";
        });
    }

    function setInc($field, $value = null)
    {
        return $this->setMore($field, '+', $value);
    }

    function setDec($field, $value = null)
    {
        return $this->setMore($field, '-', $value);
    }

    function setMore($field, $op, $value = 1)
    {
        return $this->setModel($field, $value, function ($field) use ($op) {
            return "$field = $field $op ";
        });
    }

    function setBind($set, $bind = null)
    {
        empty($set) || $this->data->set = is_null($s = $this->bind($bind)->data->set) ? $set : "$s,$set";
        return $this;
    }

    private function setModel($field, $value, $func)
    {
        if (is_array($field)) {
            foreach ($field as $k => $v) {
                $this->setModel($k, $v, $func);
            }
        } else {
            $key = self::bindKey($field) . '_S';
            $field = self::unquote($field);
            $set = call_user_func($func, $field) . ' ' . $key;
            $this->setBind($set, [$key => $value]);
        }
        return $this;
    }

    //  分组：$group1,...
    function group()
    {
        $groupArr = [];
        foreach (func_get_args() as $group) {
            $groupArr[] = self::unquote($group);
        }
        $groups = join(',', $groupArr);
        $this->data->group = is_null($g = $this->data->group) ? $groups : "$g,$groups";
        return $this;
    }

    //  顺序：$field,$order | [$field1 => $order1,...]
    function order($field, $orders = null)
    {
        if (is_array($field)) {
            foreach ($field as $k => $v) {
                $this->order($k, $v);
            }
        } else {
            $orders = self::unquote($field) . " $orders";
            $this->data->order = is_null($o = $this->data->order) ? $orders : "$o,$orders";
        }
        return $this;
    }

    //  限制：$from,$num | $num
    function limit($from, $num = null)
    {
        $limit = [$from];
        is_null($num) ? array_unshift($limit, 0) : array_push($limit, $num);
        $this->data->limit = $limit;
        return $this;
    }

    function limitOne()
    {
        $this->data->limit = is_null($limit = $this->data->limit) ? [0, 1] : [$limit[0], 1];
        return $this;
    }

    //+----------------------------------------------------------------------
    //  获取SQL：增、删、改、查、模型
    //+----------------------------------------------------------------------

    // 数据库定位
    function position($address, $dbn)
    {
        $this->data->address = $address;
        $this->data->dbn = $dbn;
        return $this;
    }

    function insertSql($address, $dbn)
    {
        return $this->queryModel(function () use ($address, $dbn) {
            $this->position($address, $dbn);
            return 'INSERT INTO' . $this->parseTable() . $this->parseValues();
        });
    }

    function deleteSql()
    {
        return $this->queryModel(function () {
            return 'DELETE FROM' . $this->parseTable() . $this->parseSql('where') . $this->parseSql('group') . $this->parseSql('order') . $this->parseSql('limit');
        });
    }

    function updateSql()
    {
        return $this->queryModel(function () {
            return 'UPDATE' . $this->parseTable() . $this->parseSql('set') . $this->parseSql('where') . $this->parseSql('group') . $this->parseSql('order') . $this->parseSql('limit');
        });
    }

    function selectSql()
    {
        return $this->queryModel(function () {
            return 'SELECT' . $this->parseSql('field') . ' FROM' . $this->parseTable() . $this->parseSql('where') . $this->parseSql('group') . $this->parseSql('order') . $this->parseSql('limit');
        });
    }

    private function queryModel($func)
    {
        $sql = call_user_func($func, $this);
        $bind = $this->data->bind;
        $this->data->clearData();
        return [$sql, $bind];
    }

    //+----------------------------------------------------------------------
    //  解析SQL：表、表结构、修改字段、插入字段、指定SQL
    //+----------------------------------------------------------------------

    protected function parseTable($rArr = false)
    {
        if (is_null($this->data->tableArr) && is_null($this->data->tableStr)) {
            $tableStr = " `{$this->table}`" . (is_null($this->data->alias) ? "" : " AS `{$this->data->alias}`");
            $tableArr = [$this->table => is_null($this->data->alias) ? $this->data->alias : $this->table];
            $joins = $this->data->join;
            if (is_array($joins)) {
                foreach ($joins as $join) {
                    $tableStr .= " {$join['type']} JOIN `{$join['table']}` AS `{$join['alias']}` ON `" . preg_replace('/(\w+)\.(\w+)/', '`\1`.`\2`', $join['des']) . "`";
                    $tableArr[$join['table']] = $join['alias'];
                }
            }
            $this->data->tableArr = $tableArr;
            $this->data->tableStr = $tableStr;
        }
        return $rArr ? $this->data->tableArr : $this->data->tableStr;
    }

    protected function tableDesc()
    {
        if (is_null($this->data->tableDesc)) {
            $tableDesc = [];
            $address = $this->data->address;
            $dbn = $this->data->dbn;
            foreach ($this->parseTable(true) as $table => $alias) {
                $tableDesc[$alias] = DbBase::describe($address, $dbn, $table);
            }
            $this->data->tableDesc = $tableDesc;
        }
        return $this->data->tableDesc;
    }

    protected function parseValues()
    {
        // 转成二维数组批量处理
        $values = is_array(current($this->data->values)) ? $this->data->values : [$this->data->values];
        // 绑定数组
        $bind = [];
        // 字段名称数组
        $valueBefore = [];
        // 字段绑定名数组
        $valueAfter = [];
        // 表结构
        $tableDesc = $this->tableDesc();
        for ($i = 0, $c = count($values); $i < $c; $i++) {
            // 单次绑定名数组
            $after = [];
            $value = $values[$i];
            foreach ($tableDesc as $tbn => $desc) {
                foreach ($desc->list as $name => $fieldObj) {
                    // 过滤自增字段
                    if ($fieldObj->Extra === 'auto_increment') continue;
                    // 过滤不存在字段
                    if (!key_exists($name, $value)) {
                        if ($fieldObj->Null === 'NO' && is_null($fieldObj->Default)) throw new \Exception('数据表' . $tbn . '插入数据缺少必要字段' . $name);
                        $default_val = $fieldObj->Default;
                    } else {
                        $default_val = $value[$name];
                    }
                    $i > 0 || $valueBefore[] = $name;
                    $after[] = ":{$name}_V$i";
                    $bind[":{$name}_V$i"] = DbBase::parseValue($fieldObj, $default_val);
                }
                if(empty($valueBefore)) throw new \Exception('不存在的字段');
            }
            $valueAfter[] = '(' . join(',', $after) . ')';
        }
        $this->data->bind = $bind;
        return ' (`' . join('`,`', $valueBefore) . '`) VALUES ' . join(',', $valueAfter);
    }

    protected function parseSql($type)
    {
        $val = $this->data->$type;
        if ($val) {
            switch ($type) {
                case 'set':
                case 'where':
                    $text = strtoupper($type) . ' ' . $val;
                    break;
                case 'limit':
                    $text = strtoupper($type) . ' ' . join(',', $val);
                    break;
                case 'field':
                    $text = $val;
                    break;
                case 'order':
                case 'group':
                    $text = strtoupper($type) . ' BY ' . $val;
                    break;
                default:
                    $text = false;
                    break;
            }
            return $text ? ' ' . $text : '';
        } else {
            return $type === 'field' ? ' *' : '';
        }
    }

    //+----------------------------------------------------------------------
    //  获取SQL数据包
    //+----------------------------------------------------------------------
    function dataSql()
    {
        return $this->data->getData();
    }

    function makeData($data)
    {
        $this->data->clearData();
        foreach($data as $k=>$v)
        {
            $this->data->$k = $v;
        }
    }
}